#include "bwprobe.h"
#include "asynevent.h"
#include <iomanip>

#define CONGEST_CLEAR_TIMEOUT 50

#define LOSSRATIO_HRESHOLD_UP 30
#define LOSSRATIO_HRESHOLD_LOWER 5


#define MAX_BW 10*1024*1024

BWProbe::BWProbe(uint32 upperbound, uint32 lowerbound, uint32 precise)
:m_udpClient(this)
{
	m_upperbound = upperbound;
	m_lowerbound = lowerbound;
	m_upperlimt = upperbound;
	m_lowerlimt = lowerbound;
	m_precise = precise;

	m_currrate = upperbound;
	m_bInitguessed = false;
	m_seq = 1;

	m_starttick = Now_ms();

	m_udpClient.SetPacketPairMode(g_settings.m_bPacketPair);

	m_lossmode = false;

	m_lossmode_avgspd = 0;
	m_lossmode_index = 0;
}

BWProbe::~BWProbe()
{

}

void BWProbe::NotifyResult(uint32 bw)
{
	uint32 timecost = Now_ms() - m_starttick;
	
	uint32 bw_used = static_cast<uint64>(m_udpClient.GetTotal())*1000/timecost;

	LOG4CPLUS_DEBUG(g_logger, "\n\n[Probe bw = "<< byte_format(bw,'A')<<"/s]"
		<<", total sent: "<< byte_format(m_udpClient.GetTotal(),'A')
		<<", time cost: "<< timecost <<" ms"
		<<", bw used: "<<byte_format(bw_used,'A')<<"/s"
		<<"("<< std::setprecision(2)<<std::setiosflags(std::ios::fixed)
		<<bw_used*100.0/bw<<"%)\n\n");
	
	Stop();
}

void BWProbe::Run(const std::string& ip, int port)
{
	//m_starttick = Now_ms();

	if(!g_settings.m_bPacketPair)
		m_udpClient.SetRateLimit(m_currrate);
	m_udpClient.Connect(ip, port);

	LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
		<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
		<<" >>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
}

void BWProbe::Stop()
{
	event_base_loopbreak(AsynEventFramework::Instance()->GetEventBase());
}

bool BWProbe::CheckUpperLimit()
{
	if( m_udpClient.GetCalcdInterval() == 0 ){
		uint32 sendingrateMax = m_udpClient.GetSendingrate();
		LOG4CPLUS_DEBUG(g_logger,"sending rate max = " << byte_format(sendingrateMax,'A')<<"/s");
		Stop();
		return true;
	}

	return false;
}

void BWProbe::HandleResult(uint32 sendrate, uint32 recvrate, bool inc, uint32 loss)
{
	uint32 timecost = Now_ms() - m_starttick;
	LOG4CPLUS_DEBUG(g_logger,"sending rate = " << byte_format(m_udpClient.GetSendingrate(),'A')<<"/s"
		<<", bytes sent: "<<byte_format(m_udpClient.GetTotal(),'A')
		<<", time cost: "<< timecost <<" ms");

	if(g_settings.m_bRunOnce){
		Stop();
		return;
	}

	bool bLossModeInit = false;

	if( loss >= LOSSRATIO_HRESHOLD_UP && !inc &&!m_lossmode){
		//switch to loss detect mode
		LOG4CPLUS_DEBUG(g_logger,"switch to loss detect mode" );

		m_lossmode = true;
		bLossModeInit = true;
	}

	if(m_lossmode){
		m_lossmode_avgspd += recvrate;
		++m_lossmode_index;

		//set send rate to recvrate and double the probe period, until loss drop to 0, besides calculate 
		//correlation between pakcet loss and sending rate. if it their is no strong correlation, exit.
		if( loss < LOSSRATIO_HRESHOLD_LOWER ){
			LOG4CPLUS_DEBUG(g_logger,"loss already drop to 0, exit");
			NotifyResult(m_currrate);
			return;
		}

		//Abnormal value
		if( m_currrate != MAX_BW )
			m_sim.AddSample(sendrate, loss);

		if( !m_sim.IsCorrelated() ){
			LOG4CPLUS_DEBUG(g_logger,"sending rate and packet loss don't correlated, exit");
			NotifyResult(m_lossmode_avgspd/m_lossmode_index);
			return;
		}

		if(!bLossModeInit){
			//probe using recv rate
			m_upperlimt = m_currrate;
			m_currrate = recvrate;
		}else{
			//probe using upperbound
			//m_upperlimt = m_upperbound;
			m_upperlimt = MAX_BW;
			m_currrate = m_upperlimt;
		}

		m_udpClient.Reset();
		m_udpClient.SetRateLimit(m_currrate);

		LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
			<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
			<<">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
		return;

	}


	if( inc ){

		//back up last inc state
		m_last_upperbound = m_upperbound;
		m_last_lowerbound = m_lowerbound;
		m_last_upperlimt = m_upperlimt;
		m_last_lowerlimt = m_lowerlimt;
		m_last_current = m_currrate;


		if( m_upperlimt - m_lowerlimt < m_precise ){
			//LOG4CPLUS_DEBUG(g_logger, "\n\n[Probe bw = "<< byte_format(m_currrate,'A')<<"/s]");
			//Stop();
			LOG4CPLUS_DEBUG(g_logger,"Increasing trend, enough precision");
			NotifyResult(m_currrate);
		}else{
			sleep_ms(CONGEST_CLEAR_TIMEOUT);

			LOG4CPLUS_DEBUG(g_logger,"Increasing trend");

			if(m_bInitguessed){
				m_upperlimt = m_currrate;
				m_currrate = (m_upperlimt + m_lowerlimt)/2;
			}else{
				LOG4CPLUS_DEBUG(g_logger,"No initial bw estimation, use recv_rate as a hint "<<byte_format(recvrate,'A')<<"/s");
				m_upperlimt = recvrate + recvrate/2;
				m_lowerlimt = recvrate - recvrate/2;
				LOG4CPLUS_DEBUG(g_logger,"Init fast switch bw limit to ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]");
				if( m_upperlimt > m_upperbound ){
					m_upperlimt = m_upperbound;
				}
				if( m_lowerlimt < m_lowerbound ){
					m_lowerlimt = 10 *1024;
				}
				m_currrate = (m_upperlimt + m_lowerlimt)/2;
				m_bInitguessed = true;
			}

			if( m_upperlimt - m_lowerlimt < m_precise ){
				//LOG4CPLUS_DEBUG(g_logger, "\n\n[Probe bw = "<< byte_format(m_currrate,'A')<<"/s]");
				//Stop();
				NotifyResult(m_currrate);
			}else{
				m_udpClient.Reset();
				m_udpClient.SetRateLimit(m_currrate);

				LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
					<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
					<<">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
			}
		}
	}else{
		LOG4CPLUS_DEBUG(g_logger,"Non-increasing trend");

		if( m_currrate == m_upperbound ){
			//LOG4CPLUS_DEBUG(g_logger, "\n\n[Probe bw = "<< byte_format(recvrate,'A')<<"/s]\n");
			//Stop();
			//NotifyResult(recvrate);

			if(CheckUpperLimit()){
				Stop();
				return;
			}

			LOG4CPLUS_DEBUG(g_logger, "(m_currrate == m_upperbound) "<< byte_format(m_upperbound,'A')<<"/s]\n");

			m_currrate = m_currrate + m_currrate/2;
			m_upperbound = m_currrate;
			m_lowerlimt = m_upperlimt;
			m_upperlimt = m_currrate;

			LOG4CPLUS_DEBUG(g_logger,"Sendrate too slow, fast switch bw limit to ["<<m_lowerlimt<<","<<m_upperlimt<<"]");
			m_udpClient.Reset();
			m_udpClient.SetRateLimit(m_currrate);

			LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
				<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
				<<">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");

		}else if( m_upperlimt - m_lowerlimt >= m_precise ){
			//sleep_ms(CONGEST_CLEAR_TIMEOUT);

			if(CheckUpperLimit()){
				Stop();
				return;
			}

			m_lowerlimt = m_currrate;
			m_currrate = (m_upperlimt + m_lowerlimt)/2;
			m_udpClient.Reset();
			m_udpClient.SetRateLimit(m_currrate);

			LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
				<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
				<<">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
		}else{
			//LOG4CPLUS_ERROR(g_logger,"\n\n[Failed, bw = "<< byte_format(m_currrate,'A')<<"/s]\n");

			//sleep_ms(CONGEST_CLEAR_TIMEOUT);

			//m_upperlimt = m_upperbound;
			//m_lowerlimt = m_currrate;
			//m_currrate = (m_upperlimt + m_lowerlimt)/2;
			//m_udpClient.Reset();
			//m_udpClient.SetRateLimit(m_currrate);

			//LOG4CPLUS_DEBUG(g_logger, ">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
			
			//Stop();
			//LOG4CPLUS_ERROR(g_logger, "\n\n[Probe bw = "<< byte_format(m_currrate,'A')<<"/s]\n");


			LOG4CPLUS_ERROR(g_logger,"\n\n[Probe Failed using sendingrate = "<< byte_format(m_currrate,'A')<<"/s]\n");
			//This is not the normal case, should recheck, following is commment out!
			//LOG4CPLUS_DEBUG(g_logger,"Non-Increasing trend, but enough precision");
			//NotifyResult(m_currrate);

			//recheck last increasing.
			//restore state
			//back up last inc state
		    m_upperbound = m_last_upperbound;
		    m_lowerbound = m_last_current;
		    m_upperlimt = m_upperbound;
		    m_lowerlimt = m_last_current;
		    m_currrate = m_upperbound;
			m_udpClient.Reset();
			m_udpClient.SetRateLimit(m_currrate);


			LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
				<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
				<<">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
		}

		m_bInitguessed = true;
	}
}

void BWProbe::HandleError(uint32 err_code)
{
	//event_base_loopbreak(AsynEventFramework::Instance()->GetEventBase());

	if(g_settings.m_bRunOnce){
		Stop();
		return;
	}

	sleep_ms(CONGEST_CLEAR_TIMEOUT);

	m_upperlimt = m_currrate;
	m_currrate = (m_upperlimt + m_lowerlimt)/4;
	LOG4CPLUS_DEBUG(g_logger,"Too Congested, fast switch bw limit to ["<<m_lowerlimt<<","<<m_upperlimt<<"]");

	//back up last inc state
	m_last_upperbound = m_upperbound;
	m_last_lowerbound = m_lowerbound;
	m_last_upperlimt = m_upperlimt;
	m_last_lowerlimt = m_lowerlimt;
	m_last_current = m_currrate;

	if( m_upperlimt - m_lowerlimt < m_precise ){
		//LOG4CPLUS_DEBUG(g_logger, "\n\n[Probe bw = "<< byte_format(m_currrate,'A')<<"/s]");
		//Stop();
		NotifyResult(m_currrate);
	}else{
		m_udpClient.Reset();
		m_udpClient.SetRateLimit(m_currrate);

		LOG4CPLUS_DEBUG(g_logger, "["<<m_seq++<<"]"
			<<", ["<<byte_format(m_lowerlimt,'A')<<"/s"<<","<<byte_format(m_upperlimt,'A')<<"/s"<<"]"
			<<">>>>start probing using sending rate: "<< byte_format(m_currrate,'A')<<"/s\n");
	}
}